package com.vito.act.tool;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.javax.el.ExpressionFactory;
import org.activiti.engine.impl.javax.el.PropertyNotFoundException;
import org.activiti.engine.impl.javax.el.ValueExpression;
import org.activiti.engine.impl.juel.ExpressionFactoryImpl;
import org.activiti.engine.impl.juel.SimpleContext;
import org.activiti.engine.impl.persistence.deploy.DeploymentCache;
import org.activiti.engine.impl.persistence.deploy.DeploymentManager;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.pvm.process.TransitionImpl;
import org.activiti.engine.impl.task.TaskDefinition;
import org.activiti.engine.impl.util.json.JSONObject;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vito.base.util.Contants;
import com.vito.base.util.SpringApplicationContext;
import com.vito.base.util.StringUtil;
import com.vito.oa.act.constant.OaActContants;
import com.vito.oa.act.constant.OaActContants.ProcessButtonEnum;


public class OaActivitiTool{
	private static Logger log = LoggerFactory.getLogger(OaActivitiTool.class);
	
	/**
	 * 获取最新版本的流程定义
	 * 直接通过引擎latestVersion()方法，可能出现获取到多个版本问题
	 * @param key 流程processKey
	 * @return 最新版本的流程定义
	 */
	public static ProcessDefinition getLastVersionProcess(String key){
		RepositoryServiceImpl repositoryService = ActivitiTool.getRepositoryServiceImpl();
		List<ProcessDefinition> VersionList =repositoryService.createProcessDefinitionQuery()
				.processDefinitionKey(key).orderByProcessDefinitionVersion().asc().list();
		ProcessDefinition lastVersion = VersionList.get(VersionList.size()-1);
		return lastVersion;
	}

	/**
	 * 获取当前任务下的连接线的名称  用作按钮
	 * @param task
	 * @throws Exception
	 */
	public static List<Map<String,Object>> getOaButtons(String processKey,String nodeKey,String processInstanceId) throws Exception {
		RepositoryServiceImpl repositoryService = ActivitiTool.getRepositoryServiceImpl();
		ProcessDefinitionEntity definition = null;
		if(processInstanceId == null){
			List<ProcessDefinition> VersionList =repositoryService.createProcessDefinitionQuery()
					.processDefinitionKey(processKey).orderByProcessDefinitionVersion().asc().list();
			//获取最新版本流程定义
			definition = (ProcessDefinitionEntity)VersionList.get(VersionList.size()-1);

		}else{
			definition = findProcessDefinitionEntityByProcessInstanceId(processInstanceId);
		}

		if(Contants.FORM_PROCESS_START.equals(nodeKey)){
			List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
			Map<String,Object> map = new HashMap<String, Object>();
			String documentation = definition.getDescription();
			JSONObject json = null;
			if(documentation!=null&&!"".equals(documentation)){
				documentation = StringUtil.replaceLineBreak(documentation);
				json = new JSONObject(documentation);
				map.put(ActivitiContants.ACTIVITI_DOCUMENTATION, documentation.toString());
			}

			if(json!=null&&json.has(ActivitiContants.ACTIVITI_TYPE)&&json.get(ActivitiContants.ACTIVITI_TYPE).equals(ActivitiContants.ACTIVITI_PROCESS_TYPE_ASSIGN)){
				map.put("class", "assignProcess");
				map.put("name", ProcessButtonEnum.ASSIGN.nameCh);
			}else{
				map.put("class", "assignRoleProcess");
				map.put("name", ProcessButtonEnum.START.nameCh);
			}
			list.add(map);
			return list;
		}

		List<ActivityImpl> list = definition.getActivities();
		ActivityImpl activityImpl = null;
		for(ActivityImpl p : list){
			TaskDefinition def = (TaskDefinition) p.getProperty(ActivitiContants.ACTIVITI_PROCESS_TYPE_TASKDEFINITION);
			if(def!=null){
				if(nodeKey==null){
					activityImpl = p;
					break;
				}else{
					if(nodeKey.equals(def.getKey())){
						activityImpl = p;
						break;
					}
				}
			}
		}
		if(activityImpl == null){
			return null;
		}
		List<Map<String,Object>> buttons = new ArrayList<Map<String,Object>>();
		//判断是否需要出现指派按钮
		String documentation = (String)activityImpl.getProperty(ActivitiContants.ACTIVITI_DOCUMENTATION);
		JSONObject json = null;
		if(documentation!=null){
			documentation = StringUtil.replaceLineBreak(documentation);
			json = new JSONObject(documentation);
		}
		if(json!=null&&json.has(ActivitiContants.ACTIVITI_TYPE)&&json.get(ActivitiContants.ACTIVITI_TYPE).equals(ActivitiContants.ACTIVITI_PROCESS_TYPE_ASSIGN)){
			Map<String,Object> map = new HashMap<String,Object>();
			map = new HashMap<String,Object>();
			map.put("class", "assignProcess");
			map.put("name", ProcessButtonEnum.ASSIGN.nameCh);
			map.put(ActivitiContants.ACTIVITI_DOCUMENTATION, documentation.toString());
			buttons.add(map);
		}else if(json!=null&&json.has(ActivitiContants.ACTIVITI_TYPE)&&json.get(ActivitiContants.ACTIVITI_TYPE).equals(ActivitiContants.ACTIVITI_PROCESS_TYPE_ASSIGNIFISROLE)){
			Map<String,Object> map = new HashMap<String,Object>();
			map = new HashMap<String,Object>();
			map.put("class", "assignRoleProcess");
			map.put("name", ProcessButtonEnum.AGREE.nameCh);
			map.put(ActivitiContants.ACTIVITI_DOCUMENTATION, documentation.toString());
			buttons.add(map);
		}else{
			List<PvmTransition> transitions = activityImpl.getOutgoingTransitions();
			for (PvmTransition pvmTransition : transitions) {
				PvmActivity pvmact = pvmTransition.getDestination();
				String type = (String)pvmact.getProperty(ActivitiContants.ACTIVITI_TYPE);
				if(type.equals(ActivitiContants.ACTIVITI_PROCESS_TYPE_EXCLUSIVEGATEWAY)){//判断流程是否为互斥网关 互斥网关获取子按钮
					List<PvmTransition> temp = pvmTransition.getDestination().getOutgoingTransitions();
					for(PvmTransition pvm:temp){
						Map<String,Object> map = new HashMap<String,Object>();
						String documen=(String) pvm.getProperty(ActivitiContants.ACTIVITI_DOCUMENTATION);
						map.put(ActivitiContants.ACTIVITI_DOCUMENTATION,StringUtil.replaceLineBreak(documen) );//取出js
						String name = (String) pvm.getProperty("name");
						if(name==null){
							name=ProcessButtonEnum.AGREE.nameCh;
						}
						map.put("class", "assignRoleProcess");
						map.put("name", name);
						buttons.add(map);
					}
				}else{
					Map<String,Object> map = new HashMap<String,Object>();
					String documen=(String) pvmTransition.getProperty(ActivitiContants.ACTIVITI_DOCUMENTATION);
					map.put(ActivitiContants.ACTIVITI_DOCUMENTATION, StringUtil.replaceLineBreak(documen));//取出js
					String name = (String) pvmTransition.getProperty("name");
					if(name==null){
						name=ProcessButtonEnum.AGREE.nameCh;
					}
					map.put("class", "assignRoleProcess");
					map.put("name", name);
					buttons.add(map);
				}
			}
		}
		ProcessInstance p =ActivitiTool.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId).suspended().singleResult();

		Map<String,Object> refuse = new HashMap<String,Object>();
		refuse.put("id", "endProcess");
		refuse.put("name", ProcessButtonEnum.END.nameCh);
		buttons.add(refuse);

		Map<String,Object> transfer = new HashMap<String,Object>();
		transfer.put("id", "transfer");
		transfer.put("name", ProcessButtonEnum.TRANSFER.nameCh);
		buttons.add(transfer);

		if(p==null){
			Map<String,Object> map = new HashMap<String,Object>();
			map.put("id", "hangUp");
			map.put("name", ProcessButtonEnum.HANGUP.nameCh);
			buttons.add(map);
		}else{
			Map<String,Object> map = new HashMap<String,Object>();
			map.put("id", "unHangUp");
			map.put("name", ProcessButtonEnum.HANGOUT.nameCh);
			buttons.add(map);
		}
		return buttons;
	}

	/**
     * 根据流程实例ID获取流程定义
     *
     * @param taskId
     *            任务ID
     * @return
     * @throws Exception
     */
    private static ProcessDefinitionEntity findProcessDefinitionEntityByProcessInstanceId(
            String processInstanceId) throws Exception {
    	RepositoryServiceImpl  repositoryService = ActivitiTool.getRepositoryServiceImpl();
        // 取得流程定义
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity)repositoryService
        		.getDeployedProcessDefinition(ActivitiTool.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult().getProcessDefinitionId());

        if (processDefinition == null) {
            throw new Exception("流程定义未找到!");
        }
        return processDefinition;
    }

	/**
     * 根据任务ID获取流程定义
     *
     * @param taskId
     *            任务ID
     * @return
     * @throws Exception
     */
    public static ProcessDefinitionEntity findProcessDefinitionEntityByTaskId(
            String taskId) throws Exception {
    	RepositoryServiceImpl  repositoryService = ActivitiTool.getRepositoryServiceImpl();

	   TaskEntity task = (TaskEntity)ActivitiTool.getTaskService().createTaskQuery()
			   .taskId(taskId).singleResult();
	   ProcessDefinitionEntity processDefinition=null;
	   if(task != null){
	        // 取得流程定义
		   processDefinition = (ProcessDefinitionEntity)repositoryService
	                .getDeployedProcessDefinition(task.getProcessDefinitionId());
	   }
       if (processDefinition == null) {
           throw new Exception("流程定义未找到!");
       }
        return processDefinition;
    }

	/**
	 * 根据activity定义获取节点的指派变量
	 * @return
	 */
	public static String getAssignVariableNameByActivityImpl(ActivityImpl actImpl){
		if(ActivitiContants.ACTIVITI_TYPE_USERTASK.equals(actImpl.getProperty(ActivitiContants.ACTIVITI_TYPE))){
			TaskDefinition taskDef = (TaskDefinition) actImpl.getProperty("taskDefinition");
			String expressionText =  taskDef.getAssigneeExpression().getExpressionText();
			if(expressionText.startsWith("${") && expressionText.endsWith("}")){
				String variableName = expressionText.substring(2, expressionText.length()-1);
				return variableName;
			}
		}

		return null;
	}

	/**
	 * 下一个任务节点信息,
	 *
	 * 如果下一个节点为非排他网关任务则直接返回,
	 *
	 * 如果下一个节点为排他网关, 获取排他网关Id信息, 根据排他网关Id信息和execution获取流程实例排他网关Id为key的变量值,
	 * 根据变量值分别执行排他网关后线路中的el表达式, 并找到el表达式通过的线路后的用户任务信息
	 *
	 * @param ActivityImpl
	 *            getNextTask 当前流程节点
	 * @param String
	 *            elString 排他网关顺序流线段判断条件, 例如排他网关顺序留线段判断条件为${money>1000},
	 *            若满足流程启动时设置variables中的money>1000, 则流程流向该顺序流信息
	 * @param String
	 *            processInstanceId 流程实例Id信息
	 * @return
	 */
	private static ActivityImpl getNextTask(ActivityImpl currActivity, String elString, String processInstanceId) {

		PvmActivity ac = null;
		String activityId = currActivity.getId();

		Object s = null;
		// 如果遍历节点为用户任务并且节点不是当前节点信息
		if (!"exclusiveGateway".equals(currActivity.getProperty("type")) && !activityId.equals(currActivity.getId())) {
			return currActivity;
		} else {
			// 获取节点所有流向线路信息
			List<PvmTransition> outTransitions = currActivity.getOutgoingTransitions();
			List<PvmTransition> outTransitionsTemp = null;
			for (PvmTransition tr : outTransitions) {
				ac = tr.getDestination(); // 获取线路的终点节点
				// 如果流向线路为排他网关
				if ("exclusiveGateway".equals(ac.getProperty("type"))) {
					outTransitionsTemp = ac.getOutgoingTransitions();

					// 如果网关路线判断条件为空信息
					if (null == elString || elString.isEmpty()) {
						// 获取流程启动时设置的网关判断条件信息
						Object obj = getGatewayCondition(ac.getId(), processInstanceId);
						elString = null == obj ? null : obj.toString();
					}

					// 如果排他网关只有一条线路信息
					if (outTransitionsTemp.size() == 1) {
						return getNextTask((ActivityImpl) outTransitionsTemp.get(0).getDestination(), elString,
								processInstanceId);
					} else if (outTransitionsTemp.size() > 1) { // 如果排他网关有多条线路信息
						String defalutTraId = (String) ac.getProperty("default");
						PvmTransition defaultTransition = null;
						for (PvmTransition tr1 : outTransitionsTemp) {
							s = tr1.getProperty("conditionText"); // 获取排他网关线路判断条件信息
							if (null != defalutTraId && defalutTraId.equals(tr1.getDestination().getId())) {
								defaultTransition = tr1;
							}
							if (null == s) {
								continue;
							}
							// 判断el表达式是否成立
							if (isCondition(ac.getId(), s.toString().trim(), elString)) {
								return ((ActivityImpl) tr1.getDestination());
							}
						}

						return null != defaultTransition ? (
								(ActivityImpl) defaultTransition.getDestination()) : null;
					}
				} else {
					return (ActivityImpl) ac;
				}
			}
			return null;
		}
	}

	/**
	 * 查询流程启动时设置排他网关判断条件信息
	 *
	 * @param String
	 *            gatewayId 排他网关Id信息, 流程启动时设置网关路线判断条件key为网关Id信息
	 * @param String
	 *            processInstanceId 流程实例Id信息
	 * @return
	 */
	public static Object getGatewayCondition(String gatewayId, String processInstanceId) {
		Execution execut = ActivitiTool.getRuntimeService().createExecutionQuery().processInstanceId(processInstanceId).singleResult();
		return null == execut ? null : ActivitiTool.getRuntimeService().getVariable(execut.getId(), gatewayId);
	}

	/**
	 * 根据key和value判断el表达式是否通过信息
	 *
	 * @param String
	 *            key el表达式key信息
	 * @param String
	 *            el el表达式信息
	 * @param String
	 *            value el表达式传入值信息
	 * @return
	 */
	public static boolean isCondition(String key, String el, String value) {
		ExpressionFactory factory = new ExpressionFactoryImpl();
		SimpleContext context = new SimpleContext();
		context.setVariable(key, factory.createValueExpression(value, String.class));
		ValueExpression e = factory.createValueExpression(context, el, boolean.class);
		try {
			return (Boolean) e.getValue(context);
		} catch (PropertyNotFoundException exception) {

		}
		return false;
	}

	public static List<PvmTransition> clearTransition(ActivityImpl activityImpl) {
		// 存储当前节点所有流向临时变量
		List<PvmTransition> oriPvmTransitionList = new ArrayList<PvmTransition>();
		// 获取当前节点所有流向，存储到临时变量，然后清空
		List<PvmTransition> pvmTransitionList = activityImpl.getOutgoingTransitions();
		for (PvmTransition pvmTransition : pvmTransitionList) {
			oriPvmTransitionList.add(pvmTransition);
		}
		pvmTransitionList.clear();

		return oriPvmTransitionList;
	}

	public static void restoreTransition(ActivityImpl activityImpl, List<PvmTransition> oriPvmTransitionList) {
		// 清空现有流向
		List<PvmTransition> pvmTransitionList = activityImpl.getOutgoingTransitions();
		pvmTransitionList.clear();
		// 还原以前流向
		for (PvmTransition pvmTransition : oriPvmTransitionList) {
			pvmTransitionList.add(pvmTransition);
		}
	}

	/**
	 * @param entity
	 *            执行实例 在脚本任务、服务任务等可以直接获取
	 * @param targetActivity
	 *            工作流任务节点
	 * @param processData
	 *            流程参数
	 * @throws Exception
	 */
	public static List<PvmTransition> jump(final ExecutionEntity entity, ActivityImpl targetActivity,
			Map<String, Object> processData) throws Exception {
		// 获取目标节点
		ActivityImpl activity = targetActivity;
		// 清楚原来的流向路径
		// entity.getActivity().getOutgoingTransitions().clear();
		List<PvmTransition> oldTransition = clearTransition(entity.getActivity());
		// 创建新的流向
		TransitionImpl tran = entity.getActivity().createOutgoingTransition();
		// 设置目的节点
		tran.setDestination(activity);
		if (processData != null) {
			entity.setVariables(processData);
		}
		return oldTransition;
	}

	public static String getTaskAssigneeVariable(TaskDefinition taskDef) {
		if (null == taskDef) {
			return null;
		}
		Expression expression = taskDef.getAssigneeExpression();
		if (null == expression) {
			return null;
		}
		String expressionText = expression.getExpressionText();
		if (expressionText.startsWith("${") && expressionText.endsWith("}")) {
			String assign = expressionText.substring(2, expressionText.length() - 1);
			return assign;
		}
		return null;
	}

	/**
	 * 跳转到下一个用审批人的用户任务节点或者其他类型的任务节点
	 *
	 * @param execution
	 *            当前流程分支实例
	 * @param curActivity
	 *            当前任务节点
	 */
	public static Map<String,Object> jumpToNextAvailableTask(ExecutionEntity execution, ActivityImpl curActivity) {
		Map<String,Object> result = new HashMap<String,Object>();
		result.put(OaActContants.ORIGIN_TRANSITION_ACT, execution.getActivity());
		Object assignee = null;
		ActivityImpl nextActivity = null;
		String assVar = null;
		do {
			nextActivity = getNextTask(curActivity, null, execution.getProcessInstanceId());
			if (null == nextActivity || !("userTask".equals(nextActivity.getProperties().get("type")))) {
				assignee = nextActivity;
				break;
			}
			TaskDefinition taskDef = (TaskDefinition) nextActivity.getProperty("taskDefinition");
			assVar = getTaskAssigneeVariable(taskDef);

			try {
				assignee = execution.getVariable(assVar);
				if(assignee == null){
					assignee = execution.getVariable(OaActContants.ASSIGN);
				}
			} catch (PropertyNotFoundException exception) {

			}
		} while (false);
		if (null == nextActivity) {
			return null;
		}
		if (null == assignee &&  OaActContants.START_USER != assVar) {
			return jumpToNextAvailableTask(execution, nextActivity);
		}
		try {
			List<PvmTransition> outTran = jump(execution, nextActivity, null);
			result.put(OaActContants.ORIGIN_TRANSITION_TRANS, outTran);
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 清楚流程定义的缓存
	 * @param processDefinationId
	 * @return
	 */
	public static boolean removeProcessDefinitionCache(String processDefinationId){
		if(StringUtil.isEmpty(processDefinationId)){
			return false;
		}
		ProcessEngineConfigurationImpl  pecImpl = (ProcessEngineConfigurationImpl) ActivitiTool.getProcessEngine()
			      .getProcessEngineConfiguration();
		if(null == pecImpl){
			return false;
		}
		DeploymentManager deploymentCache = pecImpl.getDeploymentManager();
		if(null == deploymentCache){
			return false;
		}
		DeploymentCache<ProcessDefinitionEntity> cache = deploymentCache.getProcessDefinitionCache();
		if(null == cache){
			return false;
		}
		cache.remove(processDefinationId);
		return true;
	}

	/** 
     * 根据任务ID获得任务实例 
     *  
     * @param taskId 
     *            任务ID 
     * @return 
     * @throws Exception 
     */  
    public static TaskEntity findTaskById(String taskId) {  
        TaskEntity task = (TaskEntity) ActivitiTool.getTaskService().createTaskQuery().taskId(  
                taskId).singleResult();    
        return task;  
    }
    
	/**
	 * 批量导入
	 * @param dir
	 */
	public static void importBpmn(String dir) {
		File fdir = new File(dir);
		if (!fdir.exists()) {
			return;
		}
		File flist[] = fdir.listFiles();
		for (File file : flist) {
			try {
				FileInputStream inputStream = new FileInputStream(file);
				ActivitiTool.getRepositoryService().createDeployment().addInputStream(file.getName(), inputStream).deploy();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}

		}
	}

	/**
	 * 导出所有流程配置
	 * @param dir
	 */
	public static void exportBpmn(String dir) {
		List<ProcessDefinition> mList = ActivitiTool.getRepositoryService().createProcessDefinitionQuery().latestVersion()
				.orderByProcessDefinitionVersion().asc().list();
		File fdir = new File(dir);
		if (!fdir.exists()) {
			fdir.mkdirs();
		}
		for (ProcessDefinition model : mList) {
			String filename = model.getKey() + "-" + new Date().getTime() + ".bpmn20.xml";
			try {
				byte[] xml = new BpmnXMLConverter().convertToXML(ActivitiTool.getRepositoryService().getBpmnModel(model.getId()));
				File file = new File(dir, filename);
				FileOutputStream out = null;
				out = new FileOutputStream(file);
				out.write(xml);
				out.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {

			}
		}
	}
	
	
	public static void main(String[] args) {
		SpringApplicationContext.setClassPathXmlApplicationContext("conf/conf-application.xml");
		RepositoryServiceImpl repositoryService = ActivitiTool.getRepositoryServiceImpl();
		List<ProcessDefinition> post =repositoryService.createProcessDefinitionQuery().processDefinitionKey("document_post").latestVersion().list();
		for(ProcessDefinition p : post){
			System.out.println(p.getVersion());
		}
	}
}
